﻿using Ionic.Zip;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;

/* Author: Leslie Xin
 * E-Mail: lesliexin@outlook.com
 * WebSite: http://www.lesliexin.com
 * Datetime: 2025-06-15
 * 
 * V1
 */

namespace LeslieXin.SimpleZip
{
    /// <summary>
    /// 简易ZIP压缩、解压缩
    /// </summary>
    public class SimpleZip
    {
        /// <summary>
        /// 压缩文件/文件夹列表
        /// </summary>
        public class CList
        {
            /// <summary>
            /// 压缩文件/文件夹列表
            /// </summary>
            /// <param name="sPath">文件/文件夹路径</param>
            /// <param name="bIsFolder"> true/false：是/不是文件夹</param>
            /// <param name="sFolders">子文件夹名，可以多级，没有前后的“\”<para>当为空时即将文件/文件夹内容放到根目录下。</para></param>
            public CList(string sPath, bool bIsFolder, string sFolders)
            {
                this.sPath = sPath;
                this.bIsFolder = bIsFolder;
                this.sFolders = sFolders;
            }

            /// <summary>
            /// 文件/文件夹路径
            /// </summary>
            public string sPath { get; set; }
            /// <summary>
            /// true/false：是/不是文件夹
            /// </summary>
            public bool bIsFolder { get; set; }
            /// <summary>
            /// 子文件夹名，可以多级，没有前后的“\”<para>当为空时即将文件/文件夹内容放到根目录下。</para>
            /// </summary>
            public string sFolders { get; set; }
        }


        /// <summary>
        /// 解压缩ZIP文件<para>解压前记得判断磁盘剩余空间</para>
        /// </summary>
        /// <param name="sZipPath">zip文件完整路径</param>
        /// <param name="sToFolder">待解压目录。不带最后的“\”<para>如果目录不存在则自动新建。</para><para>如果目录中存在压缩包内文件则会被覆盖</para></param>
        /// <param name="sPsw">压缩包密码。为空（默认）则代表无密码。</param>
        /// <param name="bUseZipName">true：在解压目录下新建以压缩包名为名称的文件夹<para>false（默认）：解压到解压目录下</para></param>
        /// <param name="progress">解压缩进度<para>key：当前解压进度</para><para>value：当前解压缩文件/文件夹名</para></param>
        /// <returns>空字符串/非空字符串：成功/失败及失败信息</returns>
        public static string UnZip(string sZipPath, string sToFolder, string sPsw = "", bool bUseZipName = false, IProgress<KeyValuePair<double, string>> progress = null)
        {
            try
            {
                sZipPath = sZipPath.MaxPath();
                string sZx = sToFolder;
                if (bUseZipName)
                {
                    sZx = sToFolder + "\\" + Path.GetFileNameWithoutExtension(sZipPath);
                }
                sZx = sZx.MaxPath();
                if (!Directory.Exists(sZx))
                    Directory.CreateDirectory(sZx);

                if (progress != null)
                    progress.Report(new KeyValuePair<double, string>(0, ""));
                using (ZipFile zip = new ZipFile(sZipPath, Encoding.Default))
                {
                    if (!string.IsNullOrEmpty(sPsw))
                        zip.Password = sPsw;
                    zip.ExtractProgress += (sender, e) => {
                        if (e.EntriesTotal != 0)
                        {
                            if (progress != null)
                                progress.Report(new KeyValuePair<double, string>(
                                    e.EntriesExtracted * 100.0d / e.EntriesTotal,
                                    e.CurrentEntry == null ? "" : e.CurrentEntry.FileName));
                        }
                    };
                    zip.ExtractAll(sZx, ExtractExistingFileAction.OverwriteSilently);
                }
                return "";
            }
            catch (Exception ex)
            {
                return ex.Message;
            }
        }

        /// <summary>
        /// 压缩文件/文件夹到ZIP<para>压缩前记得判断磁盘剩余空间</para>
        /// </summary>
        /// <param name="lscList">文件/文件夹列表</param>
        /// <param name="sZipPath">压缩后zip文件完整路径。后缀：.zip。<para>如果目录不存在则自动新建。</para><para>如果zip文件存在，则会先删除再生成</para></param>
        /// <param name="sPsw">压缩包密码。为空（默认）则代表不设置密码</param>
        /// <param name="progress">解压缩进度<para>key：当前解压进度</para><para>value：当前解压缩文件/文件夹名</para></param>
        /// <param name="splitSize">分卷压缩大小（MB）,小于等于0（默认）代表不进行分卷压缩</param>
        /// <returns>空字符串/非空字符串：成功/失败及失败信息</returns>
        public static string Zip(List<CList> lscList, string sZipPath, string sPsw = "", long splitSize = -1, IProgress<KeyValuePair<double, string>> progress = null)
        {
            if (lscList.Count > 0)
            {
                try
                {
                    sZipPath = sZipPath.MaxPath();
                    File.Delete(sZipPath);
                    if (progress != null)
                        progress.Report(new KeyValuePair<double, string>(0, ""));
                    using (ZipFile zip = new ZipFile(Encoding.Default))
                    {
                        if (!string.IsNullOrEmpty(sPsw))
                        {
                            zip.Password = sPsw;
                        }
                        zip.SaveProgress += (sender, e) => {
                            if (e.EntriesTotal != 0)
                            {
                                if (progress != null)
                                    progress.Report(new KeyValuePair<double, string>(
                                        e.EntriesSaved * 100.0d / e.EntriesTotal,
                                        e.CurrentEntry == null ? "" : e.CurrentEntry.FileName));
                            }
                        };

                        foreach (CList clist in lscList)
                        {
                            if (clist.bIsFolder)
                            {
                                zip.AddDirectory(clist.sPath.MaxPath(), clist.sFolders);
                            }
                            else
                            {
                                zip.AddFile(clist.sPath.MaxPath(), clist.sFolders);
                            }
                        }
                        if (!Directory.Exists(Path.GetDirectoryName(sZipPath)))
                            Directory.CreateDirectory(Path.GetDirectoryName(sZipPath));
                        var ls = zip.Entries.ToList();
                        for (int i = ls.Count - 1; i >= 0; i--)
                        {
                            var p = sZipPath + "\\" + ls[i].FileName;
                            if (p.Length > 248)
                            {
                                Type controlType = ls[i].GetType();
                                var pi = controlType.GetField("_LocalFileName", BindingFlags.Instance | BindingFlags.NonPublic);
                                var value = pi.GetValue(ls[i]).ToString();
                                pi.SetValue(ls[i], value.MaxPath());
                            }
                        }
                        //支持压缩大于4GB文件
                        zip.UseZip64WhenSaving = Zip64Option.AsNecessary;
                        if (splitSize > 0)
                        {
                            zip.MaxOutputSegmentSize64 = splitSize * 1020 * 1024;
                        }
                        zip.Save(sZipPath);
                    }
                    return "";
                }
                catch (Exception ex)
                {
                    return ex.Message;
                }
            }
            else
            {
                return "没有文件或文件夹需要压缩";
            }
        }

    }


    public static class LX_FW_EXTEND
    {

        /// <summary>
        /// 超长路径处理
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public static string MaxPath(this string path)
        {
            try
            {
                if (string.IsNullOrEmpty(path)) return path;
                if (path.StartsWith(@"\\?\")) return path;
                if (path.Length >= 248) // 260是文件路径，248是目录路径
                {
                    path = @"\\?\" + path;
                }
                return path;
            }
            catch (Exception)
            {
                return path;
            }
        }

    }
}
